package com.clp.protocol.iec104.server.pipeline.state.control;

import com.clp.protocol.iec104.apdu.IApdu;
import com.clp.protocol.iec104.apdu.SApdu;
import com.clp.protocol.iec104.apdu.UApdu;
import com.clp.protocol.iec104.definition.UCtrlType;
import com.clp.protocol.iec104.server.SlaveControlConfig;
import com.clp.protocol.iec104.server.pipeline.PipelineManager;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.Nullable;
import java.util.concurrent.TimeUnit;

@Slf4j
public class T0T1T2T3ControlStateHandler extends AbstractControlStateHandler {
    @Getter
    private final int t0;
    private final int t0Ms;
    @Getter
    private final int t1;
    private final int t1Ms;
    @Getter
    private final int t2;
    private final int t2Ms;
    @Getter
    private final int t3;
    private final int t3Ms;

    private boolean ensureSendIType;       // 确认发送了I帧
    private long lastSendITypeTime;        // 上次发送I帧时间
    private boolean ensureSendSType;       // 确认发送了S帧
    private long lastSendSTypeTime;        // 上次发送S帧时间
    private boolean ensureSendUType;       // 确认发送了U帧
    private long lastSendUTypeTime;        // 上次发送U帧时间

    private long lastRecvTime;             // 上次接收帧的时间
    private boolean ensureRecvIType;       // 确认接收了I帧
    private long lastRecvITypeTime;        // 上次接收I帧时间
    private boolean ensureRecvSType;       // 确认接收了S帧
    private long lastRecvSTypeTime;        // 上次接收S帧时间
    private boolean ensureRecvUType;       // 确认接收了U帧
    private long lastRecvUTypeTime;        // 上次接收U帧时间

    private volatile boolean isTestingLink; // 是否在进行链路测试

    public T0T1T2T3ControlStateHandler(PipelineManager manager, SlaveControlConfig cfg) {
        super(manager);
        this.t0 = cfg.getT0();
        this.t0Ms = t0 * 1000;
        this.t1 = cfg.getT1();
        this.t1Ms = t1 * 1000;
        this.t2 = cfg.getT2();
        this.t2Ms = t2 * 1000;
        this.t3 = cfg.getT3();
        this.t3Ms = t3 * 1000;
    }

    @Override
    protected void resetState() {
        ensureSendIType = true;
        lastSendITypeTime = System.currentTimeMillis(); // 系统当前毫秒数
        ensureSendSType = true;
        lastSendSTypeTime = System.currentTimeMillis();
        ensureSendUType = true;
        lastSendUTypeTime = System.currentTimeMillis();
        lastRecvTime = System.currentTimeMillis();
        ensureRecvIType = true;
        lastRecvITypeTime = System.currentTimeMillis();
        ensureRecvSType = true;
        lastRecvSTypeTime = System.currentTimeMillis();
        ensureRecvUType = true;
        lastRecvUTypeTime = System.currentTimeMillis();
        isTestingLink = false;
    }

    @Override
    protected void afterResetState() {

    }

    @Override
    protected UApdu updateStateByRecv(UApdu uApdu) {
        // 更新链路状态
        lastRecvTime = System.currentTimeMillis();
        // 确认接收了U帧
        ensureRecvUType = true;
        // 更新上次接收U帧时间
        lastRecvUTypeTime = System.currentTimeMillis();

        // 设置还未发送对该帧的确认（接收到激活U帧要发送确认U帧）
        UCtrlType uCtrlType = uApdu.getUApci().getCtrlArea().getUCtrlType();
        switch (uCtrlType) {
            case U_START_DT_V:
            case U_STOP_DT_V:
                ensureSendUType = false;
                break;
            case U_TEST_FR_V:
                ensureSendUType = false;
                uApduSender().chSendTestFrC();
                break;
            case U_START_DT_C:
            case U_STOP_DT_C:
                ensureSendUType = true;
            case U_TEST_FR_C:
                ensureSendUType = true;
                if (!isTestingLink) {
                    log.warn("[Control] 接收到无效的链路测试确认");
                } else {
                    isTestingLink = false;
                }
                break;
        }

        return uApdu;
    }

    @Override
    protected SApdu updateStateByRecv(SApdu sApdu) {
        lastRecvTime = System.currentTimeMillis();
        // 确认接收了S帧
        ensureRecvSType = true;
        // 更新上次接收S帧时间
        lastRecvSTypeTime = System.currentTimeMillis();
        // 接收到S帧不需要对该帧发送认可消息
        return sApdu;
    }

    @Override
    protected IApdu updateStateByRecv(IApdu iApdu) {
        // 确认接收了I帧
        ensureRecvIType = true;
        // 更新上次接收I帧时间
        lastRecvITypeTime = System.currentTimeMillis();
        // 设置为还未发送对该I帧的确认（接收到I帧要发送I帧或S帧的认可）
        ensureSendIType = false;
        ensureSendSType = false;
        return iApdu;
    }

    @Override
    protected UApdu updateStateBySend(UApdu uApdu) {
        // 确认发送了U帧
        ensureSendUType = true;
        // 更新上次发送U帧的时间
        lastSendUTypeTime = System.currentTimeMillis();

        UCtrlType uCtrlType = uApdu.getUApci().getCtrlArea().getUCtrlType();
        switch (uCtrlType) {
            case U_START_DT_V:
            case U_STOP_DT_V:
                ensureRecvUType = false;
                break;
            case U_TEST_FR_V:
                if (isTestingLink) {
                    log.warn("当前正在进行链路测试，已取消发送链路测试");
                    return null;
                }
                isTestingLink = true;
                ensureRecvUType = false;
                break;
            default:
                ensureRecvUType = true; // 发送确认不需要再进行确认，即默认已经确认了
        }
        return uApdu;
    }

    @Override
    protected SApdu updateStateBySend(SApdu sApdu) {
        // 确认发送了S帧
        ensureSendSType = true;
        lastSendSTypeTime = System.currentTimeMillis();
        // 发送S帧不需要等待对方的认可
        return sApdu;
    }

    @Override
    protected IApdu updateStateBySend(IApdu iApdu) {
        // 确认发送了I帧
        ensureSendIType = true;
        // 更新上次发送U帧的时间
        lastSendITypeTime = System.currentTimeMillis();
        // 设置为未接收到发出的响应（发送I帧要接收到I帧或S帧的认可）
        ensureRecvSType = false;
        ensureRecvIType = false;
        return iApdu;
    }

    @Nullable
    @Override
    protected ScheduledTask getScheduledTask() {
        return new ScheduledTask(0, 1, TimeUnit.SECONDS) {

            @Override
            public void run() {
                long nowTime = System.currentTimeMillis();  // 当前时间

                // T1：如果发送了U帧后，T1时间内没有接收到U帧认可，那么认为连接出现问题，需要断开连接
                if (ensureSendUType && !ensureRecvUType && (nowTime - lastSendUTypeTime > t1Ms)) {
                    // 如果当前状态为已经发送了链路测试帧
                    if (isTestingLink) isTestingLink = false;
                    log.warn("[Control] 发送U帧后，t1时间内未接收到主站的认可消息，即将断开连接\n");
                    channel.close();
                }
                // T1：如果发送了I帧后，T1时间内没有接收到I帧或S帧认可，那么认为连接出现问题，需要断开连接
                if (ensureSendIType && !(ensureRecvIType || ensureRecvSType) && (nowTime - lastSendITypeTime > t1Ms)) {
                    log.warn("[Control] 发送I帧后，T1时间内未接收到从站的认可消息，即将断开连接\n");
                    channel.close();
                }

                // T2：如果收到I格式报文后，没有发送S帧或I帧，且T2时间内没有再接收到新的I帧，则要发送S格式帧进行认可
                if (ensureRecvIType && (!(ensureSendSType || ensureSendIType)) && (nowTime - lastRecvITypeTime > t2Ms)) {
                    log.info("[Control] T2时间后没有继续收到I格式帧，本站也没有发送S帧或I帧，发出S帧进行认可\n");
                    sApduSender().chSend(inSlaveChannel().controlInfo().recvSeq()); // 发送S帧
                }

                // T3：如果接收到任意格式的帧后，T3时间内没有再接收到新的任意格式帧，则发送链路测试帧
                if ((ensureRecvUType || ensureRecvSType || ensureRecvIType) && (nowTime - lastRecvTime > t3Ms)) {
                    log.info("[Control]: T3时间内没有继续收到任何格式的帧，发送测试U帧\n");
                    uApduSender().chSendTestFrV(); // 发送链路测试激活
                }
            }
        };
    }
}
